/*
 *
 *  * // Copyright (c) 2024 KylinOS. All rights reserved.
 *  * //
 *  * // This program is free software; you can redistribute it
 *  * // and/or modify it under the terms of the GNU General Public
 *  * // License (version 2) as published by the FSF - Free Software
 *  * // Foundation*
 *
 */

/*
 *  * // Copyright (C) 2024 ben LG <guoliben@kylinos.cn>
 */

package doctask

import (
	"bufio"
	"bytes"
	"encoding/binary"
	"fmt"
	"io"
	"io/ioutil"
)

// 定义OLE相关常量
const (
	OleSignature        = "\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1"
	DirSectOffset       = 0x30 // 目录扇区偏移位置
	HeaderSize          = 512  // OLE头大小
	DirEntrySize        = 128  // 目录项大小
	SectorSize          = 512  // 扇区大小
	MaxDirectoryEntries = 100  // 最大目录项扫描数
	DocStreamSignature  = "WordDocument"
	ZeroTableSignature  = "0Table"
	OneTableSignature   = "1Table"
	WpsCustomSignature  = "WpsCustomData"
	WpsCustOffDefault   = 14
)
const fibSize = 0x652 // 1618 bytes

// DirectoryEntry 代表CFB中的目录项
// OLE 目录项结构
type DirectoryEntry struct {
	Name        string
	StreamStart uint32
	StreamSize  uint32
	ObjectType  byte
	NameLength  uint16
}
type WordFIB struct {
	FIdent       uint16
	FEncrypte    bool
	FWhitchTable uint16
	FStart       uint32
	FEnd         uint32
	FIBSize      uint32
	CLXOffset    uint32
}
type CLXData struct {
	Fc           uint32
	FcCompressed bool
}

// 提取文本 文件
func ProcessFile(path string) ([]byte, error) {
	content, err := ioutil.ReadFile(path)
	if err != nil {
		return nil, err
	}
	text, err := ProcessBuffer(content)
	if err != nil {
		return nil, err
	}
	return text, nil
}

// 提取文本 流
func ProcessBuffer(data []byte) ([]byte, error) {
	// 1. 创建二进制流读取器
	reader := bufio.NewReader(bytes.NewReader(data))

	// 2. OLE格式验证
	if !isValidOLE(reader) {
		return nil, fmt.Errorf("非OLE格式文件")
	}

	reader = bufio.NewReader(bytes.NewReader(data[DirSectOffset:]))
	// 3. 获取目录流位置
	dirOffset, err := getDirectoryOffset(reader)
	if err != nil {
		return nil, fmt.Errorf("目录定位失败: %w", err)
	}
	reader = bufio.NewReader(bytes.NewReader(data[dirOffset:]))

	// 4. 查找数据流
	streams, err := findDocStream(reader)
	if streams == nil {
		return nil, fmt.Errorf("流查找失败1: %w", err)
	}
	if err != nil && err != io.EOF {
		return nil, fmt.Errorf("流查找失败2: %w", err)
	}
	docStream := streams[DocStreamSignature]
	if docStream == nil {
		return nil, fmt.Errorf("流查找失败3: %w", err)
	}
	// 5. 查找WorkFIB  文本地址
	fibOffset := (int64(docStream.StreamStart) + 1) * SectorSize

	fib := parseWordFIB(data[fibOffset:])
	if fib.FIdent == 0 {
		if _, ok := streams[WpsCustomSignature]; ok {
			fibOffset = (int64(WpsCustOffDefault) + 1) * SectorSize
			fib = parseWordFIB(data[fibOffset:])
			if fib.FIdent == 0 {
				return nil, fmt.Errorf("获取FIB失败")
			}
		} else {
			return nil, fmt.Errorf("获取FIB失败")
		}
	}
	textRangeStart := int64(fib.FStart) + fibOffset
	textRangeEnd := int64(fib.FEnd) + fibOffset

	// 6. 获取数据表 fc.clx 用于计算数据存储格式 utf8/unicode6
	tableStream := getTable(fib, streams)
	if tableStream == nil {
		return nil, fmt.Errorf("未找到 tableStream")
	}
	tableDataOffset := (int64(tableStream.StreamStart) + 1) * SectorSize
	tableDataEnd := tableDataOffset + int64(tableStream.StreamSize)
	// 7. 检查 tableData 越界
	if tableDataOffset < 0 || tableDataOffset >= int64(len(data)) {
		return nil, fmt.Errorf("tableDataOffset 越界: %d, len=%d", tableDataOffset, len(data))
	}
	if tableDataEnd < tableDataOffset {
		return nil, fmt.Errorf("tableDataEnd < tableDataOffset: %d < %d", tableDataEnd, tableDataOffset)
	}
	if tableDataEnd > int64(len(data)) {
		tableDataEnd = int64(len(data)) // 截断，防越界
	}
	tableData := data[tableDataOffset:tableDataEnd]

	// 8. 检查 CLX 偏移越界
	if fib.CLXOffset < 0 || int(fib.CLXOffset)+21 > len(tableData) {
		return nil, fmt.Errorf("CLXOffset 越界: CLXOffset=%d, tableDataLen=%d", fib.CLXOffset, len(tableData))
	}
	clxData := tableData[fib.CLXOffset : fib.CLXOffset+21]
	fc := parseClx(clxData)

	// 9. 解析内容
	var totalText bytes.Buffer
	parseDocContent(&totalText, data[textRangeStart:textRangeEnd], fc)

	return totalText.Bytes(), nil
}

/********以下是内部函数******/
// OLE格式验证
func isValidOLE(reader *bufio.Reader) bool {
	header := make([]byte, len(OleSignature))
	n, _ := io.ReadFull(reader, header)
	return n >= len(OleSignature) && bytes.Equal(header, []byte(OleSignature))
}

// 获取目录偏移量
func getDirectoryOffset(reader *bufio.Reader) (int64, error) {
	buffer := make([]byte, 4)
	if _, err := io.ReadFull(reader, buffer); err != nil {
		return 0, err
	}
	dirOffset := int64(binary.LittleEndian.Uint32(buffer))
	return (dirOffset + 1) * HeaderSize, nil
}
func parseDirectoryEntry(entry []byte) DirectoryEntry {
	name := decodeUTF16LE(entry[:64])
	streamStart := binary.LittleEndian.Uint32(entry[116:120])
	streamSize := binary.LittleEndian.Uint32(entry[120:124])
	objectType := entry[66]
	nameLength := binary.LittleEndian.Uint16(entry[64:66])
	return DirectoryEntry{
		Name:        name,
		StreamStart: streamStart,
		StreamSize:  streamSize,
		ObjectType:  objectType,
		NameLength:  nameLength,
	}
}

// 解析CLX
func parseClx(clx []byte) CLXData {
	fcCompressed := false //默认unicode16
	fc := binary.LittleEndian.Uint32(clx[15:19])

	// 获取fc 文本字节长度
	fcFc := fc >> 1

	// 获取是否压缩
	compressed := (fc >> 30) & 1
	if compressed == 1 { // utf8
		fcCompressed = true
	}

	return CLXData{
		Fc:           fcFc,
		FcCompressed: fcCompressed,
	}
}

// 获取FIB
func parseWordFIB(fib []byte) WordFIB {
	// 类型判断 #ECA5 42476
	wIdent := binary.LittleEndian.Uint16(fib[0:2])
	if wIdent != 0xA5EC {
		return WordFIB{}
	}

	// 获取 Word 版本 #C100 193
	nFib := binary.LittleEndian.Uint16(fib[2:4])

	// 按bit解析
	bitInfo := binary.LittleEndian.Uint16(fib[10:12])

	// 是否加密 取第9位，从右往左移动8位
	isEncrypted := (bitInfo >> 8) & 1
	if isEncrypted == 1 {
		fmt.Println("加密文档")
		return WordFIB{}
	}
	// 获取0表/1表
	tableNo := (bitInfo >> 9) & 1

	// 读取正文起始与结束偏移
	fcMin := binary.LittleEndian.Uint32(fib[0x18:0x1C])
	fcMax := binary.LittleEndian.Uint32(fib[0x1C:0x20])
	// 读取clx
	clxOffset := binary.LittleEndian.Uint32(fib[0x1A2:0x1A6])
	return WordFIB{
		FIdent:       nFib,
		FEncrypte:    false,
		FWhitchTable: tableNo,
		FStart:       fcMin,
		FEnd:         fcMax,
		FIBSize:      fibSize,
		CLXOffset:    clxOffset,
	}
}

// 解析 UTF-16 LE 编码的字符串
func decodeUTF16LE(data []byte) string {
	u16 := make([]uint16, len(data)/2)
	_ = binary.Read(bytes.NewReader(data), binary.LittleEndian, &u16)
	var runes []rune
	for _, v := range u16 {
		if v == 0 {
			break
		}
		runes = append(runes, rune(v))
	}
	return string(runes)
}

// 解析 UTF-8 LE 编码的字符串
func decodeUTF8LE(data []byte) string {
	return string(data)
}

// 查找流--文档
func findDocStream(reader *bufio.Reader) (map[string]*DirectoryEntry, error) {
	var entries map[string]*DirectoryEntry
	entries = make(map[string]*DirectoryEntry)
	for i := 0; i < MaxDirectoryEntries; i++ {
		entryData := make([]byte, DirEntrySize)
		if _, err := io.ReadFull(reader, entryData); err != nil {
			return entries, err
		}
		entry := parseDirectoryEntry(entryData)

		if entry.Name == DocStreamSignature ||
			entry.Name == ZeroTableSignature ||
			entry.Name == OneTableSignature ||
			entry.Name == WpsCustomSignature {
			entries[entry.Name] = &entry
		}
	}
	return entries, nil
}

// 获取数据0表或1表
func getTable(fib WordFIB, entries map[string]*DirectoryEntry) *DirectoryEntry {
	var tableEntry *DirectoryEntry
	switch fib.FWhitchTable {
	case 0: // 0Table获取数据
		if entries[ZeroTableSignature] != nil {
			tableEntry = entries[ZeroTableSignature]
		}
	case 1: // 1Table获取数据
		if entries[OneTableSignature] != nil {
			tableEntry = entries[OneTableSignature]
		}
	default:
		if entries[OneTableSignature] != nil {
			tableEntry = entries[OneTableSignature]
		}
	}
	return tableEntry
}

func getWordFIB(data []byte) {
	hexStr := fmt.Sprintf("% 02x", data[:8])
	fmt.Println(hexStr) // 输出连续字符串（如"1a2b3c"）
}

// 提取文本内容
func parseDocContent(totalText *bytes.Buffer, data []byte, fc CLXData) {
	//hexStr := fmt.Sprintf("% 02x", data[:8])
	//fmt.Println(hexStr) // 输出连续字符串（如"1a2b3c"）

	buffer := bytes.NewBuffer(data)
	// 读取文件内容
	contentChunk := make([]byte, len(data))
	if n, err := io.ReadFull(buffer, contentChunk); n <= 0 || (err != nil && err != io.EOF) {
		return
	}
	if fc.FcCompressed {
		totalText.WriteString(decodeUTF8LE(contentChunk))
	} else {
		totalText.WriteString(decodeUTF16LE(contentChunk))
	}

	return
}
